﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using Kooboo.HtmlFunc.Function;

namespace Kooboo.HtmlFunc.Analysis
{
    public abstract class VisitorBase
    {
        static List<FuncType> FuncTypeList;

        static VisitorBase()
        {
            FuncTypeList = new List<FuncType>();
            var names = Enum.GetNames(typeof(FuncType));
            foreach (var val in names)
                FuncTypeList.Add((FuncType)Enum.Parse(typeof(FuncType), val));
        }

        public VisitorBase()
            : this(null)
        {
        }

        public VisitorBase(VisitorContext context)
            : this(context, FuncType.None)
        {
        }

        public VisitorBase(VisitorContext context, FuncType stype)
        {
            this.Context = context;
            this.FuncType = stype;
        }

        public VisitorContext Context
        {
            get;
            set;
        }

        public FuncType FuncType
        {
            get;
            set;
        }

        //private IFuncTester _attributeTester;
        //protected virtual IFuncTester AttributeTester
        //{
        //    get
        //    {
        //        if (_attributeTester == null)
        //            _attributeTester = new AttributeTester(string.Empty, string.Empty/*this.Func.AttributeName, this.Func.AttributeValue*/);
        //        return _attributeTester;
        //    }
        //}

        private IFuncEntity _func;
        public IFuncEntity Func
        {
            get
            {
                if (_func == null)
                    _func = FuncPool.GetFunc(this.FuncType);
                return _func;
            }
        }

        public HtmlNode BreakAttributeNode
        {
            get;
            protected set;
        }

        public HtmlCommentNode BreakCommentNode
        {
            get;
            protected set;
        }

        public virtual void VisitAll(object obj)
        {
            if (this.BreakAttributeNode != null)
            {
                var next = this.BreakAttributeNode.NextSibling ?? this.BreakAttributeNode.ParentNode;
                if (next == obj)
                {
                    this.OnBreak(obj);
                    this.BreakAttributeNode = null;
                    this.Context.Items.Remove(this.FuncType);
                }
            }

            if (this.BreakCommentNode != null)
            {
                if (this.BreakCommentNode == obj)
                {
                    this.OnBreak(obj);
                    this.BreakCommentNode = null;
                    this.Context.Items.Remove(this.FuncType);
                }
            }
        }

        protected virtual bool ValidateRule()
        {
            // validate require
            var requires = this.RequireType;
            foreach (var type in FuncTypeList)
            {
                if (type == (type & requires))
                {
                    if (type == FuncType.None)
                        continue;

                    if (!this.Context.Items.ContainsKey(type))
                        return false;
                }
            }

            // validate exclusive
            var exclusives = this.ExclusiveType;
            foreach (var type in FuncTypeList)
            {
                if (type == (type & exclusives))
                {
                    if (type == FuncType.None)
                        continue;

                    if (this.Context.Items.ContainsKey(type))
                        return false;
                }
            }

            // ret
            return true;
        }

        protected virtual FuncType RequireType
        {
            get
            {
                return FuncType.None;
            }
        }

        protected virtual FuncType ExclusiveType
        {
            get
            {
                return FuncType.None;
            }
        }

        public virtual void OnBreak(object breakNode) { }

        public virtual void VisitDocument(HtmlDocument document) { }

        public virtual void VisitNode(HtmlNode node) { }

        public virtual void VisitAttribute(HtmlAttribute attribute) { }

        public virtual void VisitCommentNode(HtmlCommentNode commentNode) { }

        public virtual void VisitTextNode(HtmlTextNode textNode) { }
    }

    public abstract class StartEndVisitor : VisitorBase
    {
        public StartEndVisitor(VisitorContext context, FuncType stype)
            : base(context, stype)
        {
        }

        private IFuncTester _startCommentTester;
        protected virtual IFuncTester StartCommentTester
        {
            get
            {
                if (_startCommentTester == null)
                    _startCommentTester = new Testers.CommentTester(this.Func.StartComment);
                return _startCommentTester;
            }
        }

        private IFuncTester _endCommentTester;
        protected virtual IFuncTester EndCommentTester
        {
            get
            {
                if (_endCommentTester == null)
                    _endCommentTester = new Testers.CommentTester(this.Func.EndComment);
                return _endCommentTester;
            }
        }

        protected abstract object OnQualified(HtmlCommentNode startNode, HtmlCommentNode endNode);

        public override void VisitCommentNode(HtmlCommentNode commentNode)
        {
            if (this.ValidateRule() &&
                this.StartCommentTester.IsMatch(commentNode))
            {
                var endNode = this.FindCommentEndNode(commentNode);
                if (endNode != null)
                {
                    var contextMarker = this.OnQualified(commentNode, endNode);
                    if (contextMarker != null)
                    {
                        // mark context
                        this.Context.Items[this.FuncType] = contextMarker;
                        // mark
                        this.BreakCommentNode = endNode;
                    }
                }
            }
        }

        protected HtmlCommentNode FindCommentEndNode(HtmlCommentNode startNode, bool exception = true)
        {
            HtmlCommentNode endNode = null;
            var next = startNode.NextSibling;
            while (next != null)
            {
                var comment = next as HtmlCommentNode;
                if (comment != null)
                {
                    if (this.EndCommentTester.IsMatch(comment))
                    {
                        endNode = comment;
                        break;
                    }

                    if (this.StartCommentTester.IsMatch(comment))
                    {
                        endNode = null;
                        break;
                    }
                }
                next = next.NextSibling;
            }

            if (endNode == null)
            {
                if (exception && this.Context.ThrowException)
                    throw new Exceptions.CloseMissingException(this.Context.FileName, startNode);
            }

            return endNode;
        }
    }
}
